/*
 * Copyright (c) 2007 jMouse Gestures
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMouse Gestures' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jmg.gesture.checker;

import java.io.Serializable;

import com.jmg.gesture.Gesture;
import com.jmg.gesture.GestureMovement;
import com.jmg.mouse.MouseMovement;


/**
 * PercentMatchChecker class checks a certain percentage of the mouse movements
 * match the required gesture movements.
 * 
 * @author  Christopher Martin
 * @version $Id: PercentMatchChecker.java 18 2007-09-28 15:08:24Z GentlemanHal $
 */
public class PercentMatchChecker implements 
    GesturePerformedChecker,
    Serializable
{
    /** Serial Version UID. */
    private static final long serialVersionUID = 1L;
    
    
    /** Percent value to use if you don't want any gestures to ever match. */
    public static final float NEVER_MATCH_PERCENT = 2f;
    
    /** Percent value to use if you only want perfect gestures to match. */
    public static final float PERFECT_MATCH_PERCENT  = 1f;
    
    /** Default percentage used with the no argument constructor. */
    public static final float DEFAULT_MATCH_PERCENT = 0.8f;
    
    /** Percent value to use if you want all gestures to always match. */
    public static final float ALWAYS_MATCH_PERCENT = 0f;
    

    /** The minimum match percentage */
    private float minimumPercentToMatch;
    
    
    
    
    
    /** 
     * Creates a new instance of <code>PercentMatchChecker</code> using the
     * {@link #DEFAULT_MATCH_PERCENT} as the minimum match percentage.
     */
    public PercentMatchChecker() {
        this(DEFAULT_MATCH_PERCENT);
    }
    
    /**
     * Creates a new instance of <code>PercentMatchChecker</code>.
     *
     * @param minimumPercentToMatch The minimum percent of mouse movements that 
     *        must match to be considered a match for the gesture.  This should
     *        be between <code>0</code> and <code>1</code>, where 
     *        <code>0</code> means all movements will match (all movements
     *        that match by zero or more percent) and <code>1</code> means only
     *        exact movements will match.  Values less than <code>0</code> will
     *        behave the same as <code>0</code> (all movements match) whereas
     *        values greater than <code>1</code> mean no movements will ever
     *        match.
     */
    public PercentMatchChecker(float minimumPercentToMatch) {
        this.minimumPercentToMatch = minimumPercentToMatch;
    }
    
    
    
    

    /**
     * Returns the minimum match percent.
     * 
     * @return The minimum percent to match.
     */
    public float getMinimumPercentToMatch() {
        return minimumPercentToMatch;
    }

    /**
     * Sets the minimum match percent.
     * 
     * @param minimumPercentToMatch The new minimum percent to match.
     */
    public void setMinimumPercentToMatch(float minimumPercentToMatch) {
        this.minimumPercentToMatch = minimumPercentToMatch;
    }

    
    
    
    
    /**
     * Checks if the given mouse movements match the given gesture.  If 
     * <code>null</code> or zero mouse movements are passed then 
     * <code>false</code> is returned unless the minimum percent match is
     * {@link #ALWAYS_MATCH_PERCENT} or less, in which case <code>true</code>
     * is returned.
     * 
     * @param  gesture The gesture to check which can not be <code>null</code>.
     * @param  mouseMovements The mouse movements to check against.
     * @return <code>true</code> if the mouse movements passed matched the
     *         passed gesture, <code>false</code> otherwise.
     * @throws java.lang.IllegalArgumentException If a <code>null</code>
     *         argument for gesture is passed.
     */
    public boolean matched(Gesture gesture, MouseMovement... mouseMovements) 
        throws IllegalArgumentException
    {
        if (null == gesture) {
            throw new IllegalArgumentException("gesture was null!");
        }
        
        
        if (null == mouseMovements || 0 == mouseMovements.length) {
            return minimumPercentToMatch <= ALWAYS_MATCH_PERCENT;
        }
        
        
        float percentPerMatch;
        float matched = 0f;
        int mouseMovementCount = 0;
        
        if (gesture.getNumberOfDirections() > mouseMovements.length) {
            percentPerMatch = mouseMovements.length 
                                        / (float) gesture.getNumberOfDirections();
        } else {
            percentPerMatch = gesture.getNumberOfDirections() 
                                        / (float) mouseMovements.length;
        }
        
        GestureMovement[] movements = gesture.getMovements();
        float combinedLength = 0f;
        int   mappingIndex   = 0;
        
        for (int mouseIndex = 0; mouseIndex < mouseMovements.length; mouseIndex++) {
            if (mappingIndex >= gesture.getNumberOfDirections()) {
                break;
            }
            
            if (movements[mappingIndex].validAngle(mouseMovements[mouseIndex].getAngle())) {   
                combinedLength += mouseMovements[mouseIndex].getLength(); 
                mouseMovementCount++;

            } else {
                if (movements[mappingIndex].validLength(combinedLength)) {
                    /* 
                     * Must check the same mouse movement against the next 
                     * gesture movement 
                     */
                    mouseIndex--;
                    
                    matched += percentPerMatch * mouseMovementCount;
                }
                
                mappingIndex++;
                combinedLength = 0f;
                mouseMovementCount = 0;
            }

        }
        
        /* Make sure all gesture movements were checked and the final length was valid */
        if (mappingIndex == (gesture.getNumberOfDirections() - 1) &&
                movements[mappingIndex].validLength(combinedLength)) 
        {
            matched += percentPerMatch * mouseMovementCount;
        }
        
        return matched >= minimumPercentToMatch;
    }

}
